using System;
using System.Reflection;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text;
using Server;
using Server.Commands.Generic;
using Server.Network;
using Server.Menus;
using Server.Menus.Questions;
using Server.Targeting;
using CPA = Server.CommandPropertyAttribute;


namespace Server.Mobiles
{
	public class CommonTools
	{
		private static Type typeofCPA = typeof( CPA );
		private static Type typeofObject = typeof( object );
		private static Type typeofNoSort = typeof( NoSortAttribute );

		public static ArrayList BuildPropsList( Mobile from, object obj )
		{
			Type type = obj.GetType();

			PropertyInfo[] props = type.GetProperties( BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public );

			ArrayList groups = GetGroups( from, type, props );
			ArrayList list = new ArrayList();

			for ( int i = 0; i < groups.Count; ++i )
			{
				DictionaryEntry de = (DictionaryEntry)groups[i];
				ArrayList groupList = (ArrayList)de.Value;

				if ( !HasAttribute( (Type)de.Key, typeofNoSort, false ) )
					groupList.Sort( PropertySorter.Instance );

				if ( i != 0 )
					list.Add( null );

				list.Add( de.Key );
				list.AddRange( groupList );
			}

			return list;
		}

		private static ArrayList GetGroups( Mobile from, Type objectType, PropertyInfo[] props )
		{
			Hashtable groups = new Hashtable();

			for ( int i = 0; i < props.Length; ++i )
			{
				PropertyInfo prop = props[i];

				if ( prop.CanRead )
				{
					CPA attr = GetCPA( prop );

					if ( attr != null )		// && from.AccessLevel >= attr.ReadLevel
					{
						Type type = prop.DeclaringType;

						while ( true )
						{
							Type baseType = type.BaseType;

							if ( baseType == null || baseType == typeofObject )
								break;

							if ( baseType.GetProperty( prop.Name, prop.PropertyType ) != null )
								type = baseType;
							else
								break;
						}

						ArrayList list = (ArrayList)groups[type];

						if ( list == null )
							groups[type] = list = new ArrayList();

						list.Add( prop );
					}
				}
			}

			ArrayList sorted = new ArrayList( groups );

			sorted.Sort( new GroupComparer( objectType ) );

			return sorted;
		}

		private static bool HasAttribute( Type type, Type check, bool inherit )
		{
			object[] objs = type.GetCustomAttributes( check, inherit );

			return ( objs != null && objs.Length > 0 );
		}

		private class PropertySorter : IComparer
		{
			public static readonly PropertySorter Instance = new PropertySorter();

			private PropertySorter()
			{
			}

			public int Compare( object x, object y )
			{
				if ( x == null && y == null )
					return 0;
				else if ( x == null )
					return -1;
				else if ( y == null )
					return 1;

				PropertyInfo a = x as PropertyInfo;
				PropertyInfo b = y as PropertyInfo;

				if ( a == null || b == null )
					throw new ArgumentException();

				return a.Name.CompareTo( b.Name );
			}
		}

		private static CPA GetCPA( PropertyInfo prop )
		{
			object[] attrs = prop.GetCustomAttributes( typeofCPA, false );

			if ( attrs.Length > 0 )
				return attrs[0] as CPA;
			else
				return null;
		}

		private class GroupComparer : IComparer
		{
			private Type m_Start;

			public GroupComparer( Type start )
			{
				m_Start = start;
			}

			private static Type typeofObject = typeof( Object );

			private int GetDistance( Type type )
			{
				Type current = m_Start;

				int dist;

				for ( dist = 0; current != null && current != typeofObject && current != type; ++dist )
					current = current.BaseType;

				return dist;
			}

			public int Compare( object x, object y )
			{
				if ( x == null && y == null )
					return 0;
				else if ( x == null )
					return -1;
				else if ( y == null )
					return 1;

				if ( !(x is DictionaryEntry) || !(y is DictionaryEntry) )
					throw new ArgumentException();

				DictionaryEntry de1 = (DictionaryEntry)x;
				DictionaryEntry de2 = (DictionaryEntry)y;

				Type a = (Type)de1.Key;
				Type b = (Type)de2.Key;

				return GetDistance( a ).CompareTo( GetDistance( b ) );
			}
		}

		// Credit: Originally from Xanthos of RunUO
		public static string GetFriendlyClassName( string typeName )
		{
			string tname = typeName;

			for ( int index = 1; index < tname.Length; index++ )
			{
				if ( char.IsUpper( tname, index ) )
				{
					tname.Insert( index++, " " );
				}
			}

			return tname;
		}

		public static string ValueToString( object obj, PropertyInfo prop )
		{
			try
			{
				return ValueToString( prop.GetValue( obj, null ) );
			}
			catch ( Exception e )
			{
				return String.Format( "!{0}!", e.GetType() );
			}
		}

		public static string ValueToString( object o )
		{
			if ( o == null )
			{
				return "-null-";
			}
			else if ( o is string )
			{
				return String.Format( "\"{0}\"", (string)o );
			}
			else if ( o is bool )
			{
				return o.ToString();
			}
			else if ( o is char )
			{
				return String.Format( "0x{0:X} '{1}'", (int)(char)o, (char)o );
			}
			else if ( o is Serial )
			{
				Serial s = (Serial)o;

				if ( s.IsValid )
				{
					if ( s.IsItem )
					{
						return String.Format( "(I) 0x{0:X}", s.Value );
					}
					else if ( s.IsMobile )
					{
						return String.Format( "(M) 0x{0:X}", s.Value );
					}
				}

				return String.Format( "(?) 0x{0:X}", s.Value );
			}
			else if ( o is byte || o is sbyte || o is short || o is ushort || o is int || o is uint || o is long || o is ulong )
			{
				return String.Format( "{0}", o );
			}
			else if ( o is Mobile )
			{
				return String.Format( "(M) 0x{0:X} \"{1}\"", ((Mobile)o).Serial.Value, ((Mobile)o).Name );
			}
			else if ( o is Item )
			{
				return String.Format( "(I) 0x{0:X}", ((Item)o).Serial );
			}
			else if ( o is Type )
			{
				return ((Type)o).Name;
			}
			else if ( o is DateTime )
			{
				return ((DateTime)o).ToString( "dd/MMM/yyyy HH:mm" );
			}
			else
			{
				return o.ToString();
			}
		}


		public static void AnalyzeObj( Mobile from )
		{
			from.SendMessage( "Please target to analyze object properties." );
			from.Target = new AnalyzeObjTarget( from );
		}

		private class AnalyzeObjTarget : Target
		{
			public AnalyzeObjTarget( Mobile from ) : base( 6, false, TargetFlags.None )
			{
			}

			protected override void OnTarget( Mobile from, object targeted )
			{
				if ( !( targeted is Item ) && !( targeted is Mobile ) )
					return;

				ArrayList m_List = BuildPropsList( from, targeted );

				if ( m_List == null || !( m_List.Count > 0 ) )
					return;

				if ( !Directory.Exists( "web" ) )
					Directory.CreateDirectory( "web" );
	
				using ( StreamWriter op = new StreamWriter( "web/properties.html" ) )
				{

					for ( int i = 0; i < m_List.Count; i++ )
					{
						object o = m_List[i];
	
						if ( o is Type )
						{
							Type type = (Type)o;
	
							op.WriteLine( type.Name );
						}
						else if ( o is PropertyInfo )
						{
							PropertyInfo prop = (PropertyInfo)o;
	
							op.Write( prop.Name );
							op.Write( " " );
							op.WriteLine( ValueToString( targeted, prop ) );
						}
					}
				}
			}
		}

		// Thanks to Jeff, Soteric and Vorspire
		public static List<T> FindItemsOfType<T>() where T : Item
		{
			List<T> list = new List<T>();

			foreach ( Item item in World.Items.Values )
			{
				if ( item == null || item.Deleted )
					continue;

				if ( item is T )
					list.Add( (T)item );
			}

			return list;
		}

	}
}